package com.dap.utils;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 用于动态引用dubbo服务,相当于标签 dubbo:reference
 *
 * @author wshh-wudufeng
 *
 */
public class DubboReferenceContainer implements ApplicationContextAware {
    /** registry group = { interface class = { group = ReferenceConfig } } */
    private final static ConcurrentHashMap<String, ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, ReferenceConfig<?>>>> references =
            new ConcurrentHashMap<>();
    /** registry group = RegistryConfig */
    private final static ConcurrentHashMap<String, RegistryConfig> registryConfigMap = new ConcurrentHashMap<>();

    private ApplicationConfig applicationConfig;
    private final RegistryConfig defaultRegistryConfig;

    private static final String EMPTY_GROUP = "";
    private static final String SCOPE_REMOTE = "remote";
    private static final int RETRY_TIMES = 0;


    private DubboReferenceContainer(RegistryConfig registryConfigs) {
        super();
        this.defaultRegistryConfig = registryConfigs;
        references.put(defaultRegistryConfig.getGroup(), new ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, ReferenceConfig<?>>>());
        registryConfigMap.put(defaultRegistryConfig.getGroup(), defaultRegistryConfig);
    }


    public <T> T getReference(Class<T> interfaceClass) {
        return getReference(EMPTY_GROUP, interfaceClass);
    }


    public <T> T getReference(Class<T> interfaceClass, String registryGroup) {
        return getReference(EMPTY_GROUP, interfaceClass, registryGroup);
    }


    /**
     * 随机获取一个服务引用
     *
     * @param serviceGroup
     *            dubbo:reference group
     * @param interfaceClass
     * @return
     */
    public <T> T getReference(String serviceGroup, Class<T> interfaceClass) {
        return getReference(null, null, serviceGroup, interfaceClass);
    }


    public <T> T getReference(String serviceGroup, String serviceVersion, Class<T> interfaceClass) {
        return getReference(null, null, serviceGroup, interfaceClass, defaultRegistryConfig.getGroup(), serviceVersion);
    }


    public <T> T getReference(String serviceGroup, Class<T> interfaceClass, String registryGroup) {
        return getReference(null, null, serviceGroup, interfaceClass, registryGroup);
    }


    public <T> T getReference(String serviceGroup, Class<T> interfaceClass, String registryGroup, String serviceVersion) {
        return getReference(null, null, serviceGroup, interfaceClass, registryGroup, serviceVersion);
    }


    /**
     * 使用指定的ip和端口获取dubbo服务引用
     *
     */
    public <T> T getReference(String host, Integer port, Class<T> interfaceClass) {
        return getReference(host, port, EMPTY_GROUP, interfaceClass);
    }


    public <T> T getReference(String host, Integer port, Class<T> interfaceClass, String registryGroup) {
        return getReference(host, port, EMPTY_GROUP, interfaceClass, registryGroup);
    }


    public <T> T getReference(String host, Integer port, String serviceGroup, Class<T> interfaceClass) {
        return getReference(host, port, serviceGroup, interfaceClass, defaultRegistryConfig.getGroup());
    }


    public <T> T getReference(String host, Integer port, String serviceGroup, Class<T> interfaceClass, String registryGroup) {
        return this.getReference(host, port, serviceGroup, interfaceClass, registryGroup, null);
    }


    /**
     * 使用指定的ip和端口获取dubbo服务引用<br/>
     *
     * @param host
     * @param port
     * @param serviceGroup
     * @param interfaceClass
     * @param registryGroup
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getReference(String host, Integer port, String serviceGroup, Class<T> interfaceClass, String registryGroup,
                              String serviceVersion) {
        ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, ReferenceConfig<?>>> registryGroupMap = references.get(registryGroup);
        if (registryGroupMap == null) {
            registryGroupMap = new ConcurrentHashMap<>();
            if (null != references.putIfAbsent(registryGroup, registryGroupMap)) {
                registryGroupMap = references.get(registryGroup);
            }
        }

        ConcurrentHashMap<String, ReferenceConfig<?>> serviceGroupMap = registryGroupMap.get(interfaceClass);
        if (serviceGroupMap == null) {
            serviceGroupMap = new ConcurrentHashMap<>();
            if (null != registryGroupMap.putIfAbsent(interfaceClass, serviceGroupMap)) {
                serviceGroupMap = registryGroupMap.get(interfaceClass);
            }
        }

        ReferenceConfig<?> ref = serviceGroupMap.get(serviceGroup);
        if (ref == null) {
            ref = new ReferenceConfig<T>();
            ref.setApplication(applicationConfig);

            RegistryConfig registryConfig = registryConfigMap.get(registryGroup);
            if (registryConfig == null) {
                registryConfig = new RegistryConfig();
                String id = "dynamicRegistry_" + registryConfigMap.size();
                registryConfig.setId(id);
                // System.setProperty("dubbo.registry." + id + ".group",
                // registryGroup);
                registryConfig.setAddress(defaultRegistryConfig.getAddress());
                registryConfig.setProtocol(defaultRegistryConfig.getProtocol());
                registryConfig.setDefault(false);
                registryConfig.setClient(defaultRegistryConfig.getClient());
                registryConfig.setGroup(registryGroup);
                if (registryConfigMap.putIfAbsent(registryGroup, registryConfig) != null) {
                    registryConfig = registryConfigMap.get(registryGroup);
                }
            }

            ref.setRegistry(registryConfig);
            ref.setInterface(interfaceClass);
            ref.setGroup(serviceGroup);
            ref.setRetries(RETRY_TIMES);
            ref.setScope(SCOPE_REMOTE);

            if (serviceVersion != null) {
                ref.setVersion(serviceVersion);
            }

            if (serviceGroupMap.putIfAbsent(serviceGroup, ref) != null) {
                ref = serviceGroupMap.get(serviceGroup);
            }
        }

        Object result = ref.get();
        if (result == null) {
            ref.destroy();
            serviceGroupMap.remove(serviceGroup);
            return getReference(host, port, serviceGroup, interfaceClass, registryGroup);
        }

        return (T) result;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        applicationConfig = applicationContext.getBean(ApplicationConfig.class);
    }


    public RegistryConfig getDefaultRegistryConfig() {
        return defaultRegistryConfig;
    }

}
